using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows;

using E = System.Linq.Expressions.Expression;

namespace ReactiveLang
{
    using ExpressionConverter;
    using State;

    using ExpressionConverter.Util;

    public class Factory<TReturn>
    {
        public readonly IExpression<Unit> Unit = Expression.Unit;
#if CONTRACT_DEBUGGING
        public Factory()
        {
            ContractDebugging.Initialize();
        }
#endif

        Converter _converter = new Converter();

        IExpression<T> ToReactive<T>(Expression<Func<T>> expression)
        {
            return _converter.ConvertEval<IExpression<T>>(expression.Body)();
        }

        public Variable<T> Variable<T>()
        {
            return new Variable<T>();
        }
        public IExpression<T> Constant<T>(T value)
        {
            return Expression.Constant(value);
        }

        public IStatement<TReturn> Assign<TValue>(IAssignableExpression<TValue> state, IExpression<TValue> value)
        {
            Contract.Requires<ArgumentNullException>(state != null, "state");
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return AsStatement(Expression.Assign(state, value));
        }
        public IStatement<TReturn> Assign<TValue>(IAssignableExpression<TValue> state, Expression<Func<TValue>> value)
        {
            return Assign(state, ToReactive(value));
        }
        public IStatement<TReturn> Assign<TTarget, TValue>(IExpression<TTarget> target, Func<TTarget, IAssignableExpression<TValue>> stateSelector, IExpression<TValue> value)
        {
            Contract.Requires<ArgumentNullException>(target != null, "target");
            Contract.Requires<ArgumentNullException>(stateSelector != null, "stateSelector");
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return AsStatement(Expression.Assign(target, stateSelector, value));
        }
        public IStatement<TReturn> Assign<TTarget, TValue>(IExpression<TTarget> target, DependencyProperty prop, IExpression<TValue> value)
            where TTarget : DependencyObject
        {
            Contract.Requires<ArgumentNullException>(target != null, "target");
            Contract.Requires<ArgumentNullException>(prop != null, "prop");
            Contract.Requires<ArgumentNullException>(value != null, "value");

            Func<TTarget, IAssignableExpression<TValue>> stateSelector = x => Expression.CreateWithDisposable<TValue>(
                o => new State.DependencyPropertyObserverHandle(x, prop, ExpressionObserver.Create<object>(v => o.OnValue((TValue)v), o.OnException)),
                v => x.SetValue(prop, v)
            );

            return Assign<TTarget, TValue>(target, stateSelector, value);
        }
        public IStatement<TReturn> Assign<TTarget, TValue>(Expression<Func<TTarget>> target, DependencyProperty prop, Expression<Func<TValue>> value)
            where TTarget : DependencyObject
        {
            return Assign(ToReactive(target), prop, ToReactive(value));
        }
        public IStatement<TReturn> Assign<TTarget, TValue>(TTarget target, DependencyProperty prop, Expression<Func<TValue>> value)
            where TTarget : DependencyObject
        {
            return Assign(Constant(target), prop, ToReactive(value));
        }
        public IStatement<TReturn> Assign<TTarget, TValue>(TTarget target, DependencyProperty prop, TValue value)
            where TTarget : DependencyObject
        {
            return Assign(Constant(target), prop, Constant(value));
        }

        public IStatement<TReturn> If(IExpression<bool> condition, IStatement<TReturn> trueStatement, IStatement<TReturn> falseStatement = null)
        {
            Contract.Requires<ArgumentNullException>(condition != null, "condition");
            return Statement.If(condition, trueStatement, falseStatement);
        }
        public IStatement<TReturn> If(Expression<Func<bool>> condition, IStatement<TReturn> trueStatement, IStatement<TReturn> falseStatement = null)
        {
            return If(ToReactive(condition), trueStatement, falseStatement);
        }

        public IStatement<TReturn> Return(IExpression<TReturn> value)
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return Statement.Return(value);
        }
        public IStatement<TReturn> Return(Expression<Func<TReturn>> value)
        {
            return Return(ToReactive(value));
        }

        public IStatement<TReturn> Block(params IStatement<TReturn>[] statements)
        {
            Contract.Requires<ArgumentNullException>(statements != null, "statements");
            return Statement.Block(statements);
        }

        public IStatement<TReturn> Imperative<TArg>(IExpression<TArg> arg, Action<TArg> @do)
        {
            Contract.Requires<ArgumentNullException>(arg != null, "arg");
            Contract.Requires<ArgumentNullException>(@do != null, "@do");
            return Statement.Imperative<TArg, TReturn>(arg, @do);
        }
        public IStatement<TReturn> Imperative<TArg>(Expression<Func<TArg>> arg, Action<TArg> @do)
        {
            Contract.Requires<ArgumentNullException>(arg != null, "arg");
            Contract.Requires<ArgumentNullException>(@do != null, "@do");
            return Imperative(ToReactive(arg), @do);
        }
        
        public IStatement<TReturn> AsStatement<T>(IExpression<T> expression)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "observable");
            return expression.AsStatement<TReturn, T>();
        }

        public IExpression<TReturn> Call(params IStatement<TReturn>[] body)
        {
            Contract.Requires<ArgumentNullException>(body != null, "body");
            Contract.Requires(body.Length > 0);
            return Expression.Call(Block(body));
        }
    }
}